import static com.googlecode.javacv.cpp.opencv_core.*;
import static com.googlecode.javacv.cpp.opencv_imgproc.*;
import static com.googlecode.javacv.cpp.opencv_legacy.*;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

import com.googlecode.javacv.cpp.opencv_core.IplImage;
import com.googlecode.javacv.cpp.opencv_imgproc.CvHistogram;



public class ColorHistogram {
    private int numberOfBins = 256;
    private float _minRange = 0.0f;
    private float _maxRange = 180.0f;
    
    /**
     * Splits a HSV image into three channels
     * @param src input image
     * @return three channels as images
     */
	static IplImage[] splitChannels(IplImage src) {
		CvSize size = cvGetSize(src);
        IplImage channel0 = IplImage.create(size, src.depth(), 1);
        IplImage channel1 = IplImage.create(size, src.depth(), 1);
        IplImage channel2 = IplImage.create(size, src.depth(), 1);
        
        cvSplit(src, channel0, channel1, channel2, null);
        IplImage[] imgs = {channel0,channel1,channel2};
		return imgs;
	}
	
    /**
     * Creates a 1D histogram of an image and applies saturation mask
     * @param image input image
     * @param mask saturation mask
     * @return histogram of image
     */
    CvHistogram get1DHistogram(IplImage image, IplImage mask){
        // Allocate histogram object
	    int dims = 1;
	    int[]sizes = {numberOfBins};
	    int histType = CV_HIST_ARRAY;
	    float[] minMax = {_minRange, _maxRange};
	    float[][] ranges = {minMax};
	    int uniform = 1;
	    CvHistogram hist = cvCreateHist(dims, sizes, histType, ranges, uniform);

        // Compute histogram
        int accumulate = 0;
        IplImage[] imgs = {image};
        cvCalcHist(imgs, hist, accumulate, mask);
        // CLEANUP
        imgs[0].release();
        return hist;
     }
    
    /**
     * Creates a hue histogram of the image.
     * @param image input image
     * @param misSaturation saturation threshold
     * @return hue histogram of image
     */
    public CvHistogram getHueHistogram(IplImage image, int minSaturation){
    	if(image == null){
    		System.out.println("Image is required");
    		System.exit(-1);
    	}
    	if(image.nChannels() != 3){
    		System.out.println("Expecting 3 channel (color) image");
    		return null;
    		//System.exit(-1);
    	}
		IplImage hsvImage = IplImage.create(cvGetSize(image), image.depth(), 3);
		// CV_BGR2HSV     =40
		cvCvtColor(image, hsvImage, CV_BGR2HSV);
		
        // Split the 3 channels into 3 separate images
		IplImage[] hsvChannels = splitChannels(hsvImage);
		// Create saturation mask
		IplImage saturationMask = null;
		if(minSaturation > 0){
			saturationMask = IplImage.create(cvGetSize(hsvImage), IPL_DEPTH_8U, 1);
			cvThreshold(hsvChannels[1], saturationMask, minSaturation, 255, CV_THRESH_BINARY);
		}
		
        // Compute histogram of the hue channel
        CvHistogram hist = get1DHistogram(hsvChannels[0], saturationMask);
        
        // CLEANUP
        if(saturationMask != null)
        	saturationMask.release();
        hsvImage.release();
        for(int i = 0; i < hsvChannels.length; i++){
        	hsvChannels[i].release();
        }
        
		return hist;
    }
    
    /**
     * Creates an image to represent the histogram.
     * @param image input image
     * @return histogram represented as an image
     */
    BufferedImage getHistogramImage(IplImage image){
    	System.out.println("getting histogram image");
        // Output image size
        int width = numberOfBins;
        int height = numberOfBins;

        float[] hist = getHistogramAsArray(image);

        // Set highest point to 90% of the number of bins
        double scale = 0.9 / maxValue(hist) * height;

        // Create a color image to draw on
        BufferedImage canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = canvas.createGraphics();

        // Paint background
        g.setPaint(Color.WHITE);
        g.fillRect(0, 0, width, height);

        // Draw a vertical line for each bin
        g.setPaint(Color.BLUE);
        for (int i = 0; i < numberOfBins; i++) {
            int h = (int)Math.round(hist[i] * scale);
            g.drawLine(i, height - 1, i, height - h - 1);
        }
        // Cleanup
        g.dispose();
        return canvas;
    }
    
    /**
     * Computes histogram of an image.
     * @param image input image
     * @return histogram represented as an array
     */
    float[] getHistogramAsArray(IplImage image){
        // Create and calculate histogram object
        CvHistogram histogram = getHueHistogram(image, 0);
        // Extract values to an array
        float[] dest = new float[numberOfBins];
        for (int i = 0; i < numberOfBins; i++) {
            dest[i] = cvQueryHistValue_1D(histogram, i);
            System.out.print(cvQueryHistValue_1D(histogram, i) + " ");
        }
        // Release the memory allocated for histogram
        cvReleaseHist(histogram);
        return dest;
    }
    
    /**
     * 
     * @param chars
     * @return the max value in the array of chars
     */
    private  float maxValue(float[] a) {
    	float max = a[0];
    	for (int i = 0; i < a.length; i++) {
    		if (a[i] > max) {
    			max = a[i];
    		}
    	}
    	return max;
    }
}
